/*
 * Copyright (C) 2013, 2014 Brett Wooldridge
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
*/

package com.zaxxer.hikari.pool;

import static com.zaxxer.hikari.pool.ProxyConnection.DIRTY_BIT_AUTOCOMMIT;
import static com.zaxxer.hikari.pool.ProxyConnection.DIRTY_BIT_CATALOG;
import static com.zaxxer.hikari.pool.ProxyConnection.DIRTY_BIT_ISOLATION;
import static com.zaxxer.hikari.pool.ProxyConnection.DIRTY_BIT_NETTIMEOUT;
import static com.zaxxer.hikari.pool.ProxyConnection.DIRTY_BIT_READONLY;
import static com.zaxxer.hikari.pool.ProxyConnection.DIRTY_BIT_SCHEMA;
import static com.zaxxer.hikari.util.ClockSource.currentTime;
import static com.zaxxer.hikari.util.ClockSource.elapsedMillis;
import static com.zaxxer.hikari.util.ClockSource.elapsedNanos;
import static com.zaxxer.hikari.util.UtilityElf.createInstance;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;

import java.lang.management.ManagementFactory;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLTransientConnectionException;
import java.sql.Statement;
import java.util.Properties;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.atomic.AtomicReference;

import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.metrics.IMetricsTracker;
import com.zaxxer.hikari.pool.HikariPool.PoolInitializationException;
import com.zaxxer.hikari.util.DriverDataSource;
import com.zaxxer.hikari.util.PropertyElf;
import com.zaxxer.hikari.util.UtilityElf;
import com.zaxxer.hikari.util.UtilityElf.DefaultThreadFactory;

abstract class PoolBase {
	private final Logger LOGGER = LoggerFactory.getLogger(PoolBase.class);

	public final HikariConfig config;
	public IMetricsTrackerDelegate metricsTracker;
	protected final String poolName;
	/**获取连接超时时间
	 *
	 */
	long connectionTimeout;
	/**校验连接是否存活的超时时间
	 *
	 */
	long validationTimeout;

	private static final String[] RESET_STATES = { "readOnly", "autoCommit", "isolation", "catalog", "netTimeout", "schema" };
	private static final int UNINITIALIZED = -1;
	private static final int TRUE = 1;
	private static final int FALSE = 0;

	private int networkTimeout;
	private int isNetworkTimeoutSupported;
	private int isQueryTimeoutSupported;
	private int defaultTransactionIsolation;
	private int transactionIsolation;
	private Executor netTimeoutExecutor;
	private DataSource dataSource;

	private final String catalog;
	private final String schema;
	private final boolean isReadOnly;
	private final boolean isAutoCommit;

	private final boolean isUseJdbc4Validation;
	private final boolean isIsolateInternalQueries;
	private final AtomicReference<Throwable> lastConnectionFailure;

	private volatile boolean isValidChecked;

	PoolBase(final HikariConfig config) {
		this.config = config;

		this.networkTimeout = UNINITIALIZED;
		this.catalog = config.getCatalog();
		this.schema = config.getSchema();
		this.isReadOnly = config.isReadOnly();
		this.isAutoCommit = config.isAutoCommit();
		// 设置隔离级别
		this.transactionIsolation = UtilityElf.getTransactionIsolation(config.getTransactionIsolation());

		this.isQueryTimeoutSupported = UNINITIALIZED;
		this.isNetworkTimeoutSupported = UNINITIALIZED;
		this.isUseJdbc4Validation = config.getConnectionTestQuery() == null;
		this.isIsolateInternalQueries = config.isIsolateInternalQueries();

		this.poolName = config.getPoolName();
		this.connectionTimeout = config.getConnectionTimeout();
		this.validationTimeout = config.getValidationTimeout();
		this.lastConnectionFailure = new AtomicReference<>();

		initializeDataSource();
	}

	/** {@inheritDoc} */
	@Override
	public String toString() {
		return poolName;
	}

	abstract void recycle(final PoolEntry poolEntry);

	// ***********************************************************************
	// JDBC methods
	// ***********************************************************************

	void quietlyCloseConnection(final Connection connection, final String closureReason) {
		if (connection != null) {
			try {
				LOGGER.debug("{} - Closing connection {}: {}", poolName, connection, closureReason);
				try {
					setNetworkTimeout(connection, SECONDS.toMillis(15));
				} finally {
					connection.close(); // continue with the close even if setNetworkTimeout() throws
				}
			} catch (Throwable e) {
				LOGGER.debug("{} - Closing connection {} failed", poolName, connection, e);
			}
		}
	}

	boolean isConnectionAlive(final Connection connection) {
		try {
			try {
				setNetworkTimeout(connection, validationTimeout);
				final int validationSeconds = (int) Math.max(1000L, validationTimeout) / 1000;
				if (isUseJdbc4Validation) {
					// 如果连接尚未关闭且仍然有效，则返回true。驱动程序应该提交一个关于连接的查询，或者使用一些其他的机制来确认调用这个方法时连接仍然有效。
					// 驱动程序为验证连接而提交的查询将在当前事务的上下文中执行。
					// validationSeconds: 超时——等待数据库操作(以秒为单位)的时间，用于验证连接是否完成。如果超时时间在操作完成之前过期，则此方法返回false。值0表示没有向数据库操作应用超时。
					return connection.isValid(validationSeconds);
				}
				try (Statement statement = connection.createStatement()) {
					if (isNetworkTimeoutSupported != TRUE) {
						setQueryTimeout(statement, validationSeconds);
					}
					statement.execute(config.getConnectionTestQuery());
				}
			} finally {
				setNetworkTimeout(connection, networkTimeout);
				if (isIsolateInternalQueries && !isAutoCommit) {
					connection.rollback();
				}
			}
			return true;
		} catch (Exception e) {
			lastConnectionFailure.set(e);
			LOGGER.warn("{} - Failed to validate connection {} ({})", poolName, connection, e.getMessage());
			return false;
		}
	}

	Throwable getLastConnectionFailure() {
		return lastConnectionFailure.get();
	}

	public DataSource getUnwrappedDataSource() {
		return dataSource;
	}

	// ***********************************************************************
	// PoolEntry methods
	// ***********************************************************************

	PoolEntry newPoolEntry() throws Exception {
		return new PoolEntry(newConnection(), this, isReadOnly, isAutoCommit);
	}

	void resetConnectionState(final Connection connection, final ProxyConnection proxyConnection, final int dirtyBits) throws SQLException {
		int resetBits = 0;

		if ((dirtyBits & DIRTY_BIT_READONLY) != 0 && proxyConnection.getReadOnlyState() != isReadOnly) {
			connection.setReadOnly(isReadOnly);
			resetBits |= DIRTY_BIT_READONLY;
		}

		if ((dirtyBits & DIRTY_BIT_AUTOCOMMIT) != 0 && proxyConnection.getAutoCommitState() != isAutoCommit) {
			connection.setAutoCommit(isAutoCommit);
			resetBits |= DIRTY_BIT_AUTOCOMMIT;
		}

		if ((dirtyBits & DIRTY_BIT_ISOLATION) != 0 && proxyConnection.getTransactionIsolationState() != transactionIsolation) {
			connection.setTransactionIsolation(transactionIsolation);
			resetBits |= DIRTY_BIT_ISOLATION;
		}

		if ((dirtyBits & DIRTY_BIT_CATALOG) != 0 && catalog != null && !catalog.equals(proxyConnection.getCatalogState())) {
			connection.setCatalog(catalog);
			resetBits |= DIRTY_BIT_CATALOG;
		}

		if ((dirtyBits & DIRTY_BIT_NETTIMEOUT) != 0 && proxyConnection.getNetworkTimeoutState() != networkTimeout) {
			setNetworkTimeout(connection, networkTimeout);
			resetBits |= DIRTY_BIT_NETTIMEOUT;
		}

		if ((dirtyBits & DIRTY_BIT_SCHEMA) != 0 && schema != null && !schema.equals(proxyConnection.getSchemaState())) {
			connection.setSchema(schema);
			resetBits |= DIRTY_BIT_SCHEMA;
		}

		if (resetBits != 0 && LOGGER.isDebugEnabled()) {
			LOGGER.debug("{} - Reset ({}) on connection {}", poolName, stringFromResetBits(resetBits), connection);
		}
	}

	void shutdownNetworkTimeoutExecutor() {
		if (netTimeoutExecutor instanceof ThreadPoolExecutor) {
			((ThreadPoolExecutor) netTimeoutExecutor).shutdownNow();
		}
	}

	long getLoginTimeout() {
		try {
			return (dataSource != null) ? dataSource.getLoginTimeout() : SECONDS.toSeconds(5);
		} catch (SQLException e) {
			return SECONDS.toSeconds(5);
		}
	}

	// ***********************************************************************
	// JMX methods
	// ***********************************************************************

	/**
	 * Register MBeans for HikariConfig and HikariPool.
	 *
	 * @param hikariPool a HikariPool instance
	 */
	void registerMBeans(final HikariPool hikariPool) {
		if (!config.isRegisterMbeans()) {
			return;
		}

		try {
			final MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();

			final ObjectName beanConfigName = new ObjectName("com.zaxxer.hikari:type=PoolConfig (" + poolName + ")");
			final ObjectName beanPoolName = new ObjectName("com.zaxxer.hikari:type=Pool (" + poolName + ")");
			if (!mBeanServer.isRegistered(beanConfigName)) {
				mBeanServer.registerMBean(config, beanConfigName);
				mBeanServer.registerMBean(hikariPool, beanPoolName);
			} else {
				LOGGER.error("{} - JMX name ({}) is already registered.", poolName, poolName);
			}
		} catch (Exception e) {
			LOGGER.warn("{} - Failed to register management beans.", poolName, e);
		}
	}

	/**
	 * Unregister MBeans for HikariConfig and HikariPool.
	 */
	void unregisterMBeans() {
		if (!config.isRegisterMbeans()) {
			return;
		}

		try {
			final MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();

			final ObjectName beanConfigName = new ObjectName("com.zaxxer.hikari:type=PoolConfig (" + poolName + ")");
			final ObjectName beanPoolName = new ObjectName("com.zaxxer.hikari:type=Pool (" + poolName + ")");
			if (mBeanServer.isRegistered(beanConfigName)) {
				mBeanServer.unregisterMBean(beanConfigName);
				mBeanServer.unregisterMBean(beanPoolName);
			}
		} catch (Exception e) {
			LOGGER.warn("{} - Failed to unregister management beans.", poolName, e);
		}
	}

	// ***********************************************************************
	// Private methods
	// ***********************************************************************

	/**
	 * Create/initialize the underlying DataSource.
	 */
	private void initializeDataSource() {
		final String jdbcUrl = config.getJdbcUrl();
		final String username = config.getUsername();
		final String password = config.getPassword();
		final String dsClassName = config.getDataSourceClassName();
		final String driverClassName = config.getDriverClassName();
		final String dataSourceJNDI = config.getDataSourceJNDI();
		final Properties dataSourceProperties = config.getDataSourceProperties();

		DataSource dataSource = config.getDataSource();
		if (dsClassName != null && dataSource == null) {
			dataSource = createInstance(dsClassName, DataSource.class);
			PropertyElf.setTargetFromProperties(dataSource, dataSourceProperties);
		} else if (jdbcUrl != null && dataSource == null) {
			// 如果dataSource为空，就初始化dataSource
			dataSource = new DriverDataSource(jdbcUrl, driverClassName, dataSourceProperties, username, password);
		} else if (dataSourceJNDI != null && dataSource == null) {
			try {
				InitialContext ic = new InitialContext();
				dataSource = (DataSource) ic.lookup(dataSourceJNDI);
			} catch (NamingException e) {
				throw new PoolInitializationException(e);
			}
		}

		if (dataSource != null) {
			setLoginTimeout(dataSource);
			createNetworkTimeoutExecutor(dataSource, dsClassName, jdbcUrl);
		}

		this.dataSource = dataSource;
	}

	/**创建新连接
	 * Obtain connection from data source.
	 *
	 * @return a Connection connection
	 */
	private Connection newConnection() throws Exception {
		final long start = currentTime();

		Connection connection = null;
		try {
			String username = config.getUsername();
			String password = config.getPassword();

			connection = (username == null) ? dataSource.getConnection() : dataSource.getConnection(username, password);
			if (connection == null) {
				throw new SQLTransientConnectionException("DataSource returned null unexpectedly");
			}

			setupConnection(connection);
			lastConnectionFailure.set(null);
			return connection;
		} catch (Exception e) {
			if (connection != null) {
				quietlyCloseConnection(connection, "(Failed to create/setup connection)");
			} else if (getLastConnectionFailure() == null) {
				LOGGER.debug("{} - Failed to create/setup connection: {}", poolName, e.getMessage());
			}

			lastConnectionFailure.set(e);
			throw e;
		} finally {
			// tracker will be null during failFast check
			if (metricsTracker != null) {
				metricsTracker.recordConnectionCreated(elapsedMillis(start));
			}
		}
	}

	/**
	 * Setup a connection initial state.
	 *
	 * @param connection a Connection
	 * @throws ConnectionSetupException thrown if any exception is encountered
	 */
	private void setupConnection(final Connection connection) throws ConnectionSetupException {
		try {
			if (networkTimeout == UNINITIALIZED) {
				networkTimeout = getAndSetNetworkTimeout(connection, validationTimeout);
			} else {
				setNetworkTimeout(connection, validationTimeout);
			}

			connection.setReadOnly(isReadOnly);
			connection.setAutoCommit(isAutoCommit);

			checkDriverSupport(connection);

			if (transactionIsolation != defaultTransactionIsolation) {
				// 如果隔离级别和默认隔离级别不相等，则重新设置connection对象的隔离级别
				connection.setTransactionIsolation(transactionIsolation);
			}

			if (catalog != null) {
				connection.setCatalog(catalog);
			}

			if (schema != null) {
				connection.setSchema(schema);
			}

			executeSql(connection, config.getConnectionInitSql(), true);

			setNetworkTimeout(connection, networkTimeout);
		} catch (SQLException e) {
			throw new ConnectionSetupException(e);
		}
	}

	/**执行isValid()或连接测试查询
	 * <br/>Execute isValid() or connection test query.
	 *
	 * @param connection a Connection to check
	 */
	private void checkDriverSupport(final Connection connection) throws SQLException {
		if (!isValidChecked) {
			try {
				if (isUseJdbc4Validation) {
					connection.isValid(1);
				} else {
					executeSql(connection, config.getConnectionTestQuery(), false);
				}
			} catch (Throwable e) {
				LOGGER.error(
						"{} - Failed to execute" + (isUseJdbc4Validation ? " isValid() for connection, configure" : "")
								+ " connection test query ({}).",
						poolName, e.getMessage());
				throw e;
			}

			try {
				defaultTransactionIsolation = connection.getTransactionIsolation();
				if (transactionIsolation == -1) {
					transactionIsolation = defaultTransactionIsolation;
				}
			} catch (SQLException e) {
				LOGGER.warn("{} - Default transaction isolation level detection failed ({}).", poolName,
						e.getMessage());
				if (e.getSQLState() != null && !e.getSQLState().startsWith("08")) {
					throw e;
				}
			}

			isValidChecked = true;
		}
	}

	/**
	 * Set the query timeout, if it is supported by the driver.
	 *
	 * @param statement  a statement to set the query timeout on
	 * @param timeoutSec the number of seconds before timeout
	 */
	private void setQueryTimeout(final Statement statement, final int timeoutSec) {
		if (isQueryTimeoutSupported != FALSE) {
			try {
				statement.setQueryTimeout(timeoutSec);
				isQueryTimeoutSupported = TRUE;
			} catch (Throwable e) {
				if (isQueryTimeoutSupported == UNINITIALIZED) {
					isQueryTimeoutSupported = FALSE;
					LOGGER.info("{} - Failed to set query timeout for statement. ({})", poolName, e.getMessage());
				}
			}
		}
	}

	/**
	 * Set the network timeout, if <code>isUseNetworkTimeout</code> is
	 * <code>true</code> and the driver supports it. Return the pre-existing value
	 * of the network timeout.
	 *
	 * @param connection the connection to set the network timeout on
	 * @param timeoutMs  the number of milliseconds before timeout
	 * @return the pre-existing network timeout value
	 */
	private int getAndSetNetworkTimeout(final Connection connection, final long timeoutMs) {
		if (isNetworkTimeoutSupported != FALSE) {
			try {
				final int originalTimeout = connection.getNetworkTimeout();
				connection.setNetworkTimeout(netTimeoutExecutor, (int) timeoutMs);
				isNetworkTimeoutSupported = TRUE;
				return originalTimeout;
			} catch (Throwable e) {
				if (isNetworkTimeoutSupported == UNINITIALIZED) {
					isNetworkTimeoutSupported = FALSE;

					LOGGER.info("{} - Driver does not support get/set network timeout for connections. ({})", poolName,
							e.getMessage());
					if (validationTimeout < SECONDS.toMillis(1)) {
						LOGGER.warn(
								"{} - A validationTimeout of less than 1 second cannot be honored on drivers without setNetworkTimeout() support.",
								poolName);
					} else if (validationTimeout % SECONDS.toMillis(1) != 0) {
						LOGGER.warn(
								"{} - A validationTimeout with fractional second granularity cannot be honored on drivers without setNetworkTimeout() support.",
								poolName);
					}
				}
			}
		}

		return 0;
	}

	/**如果isUseNetworkTimeout为true且驱动程序支持，则需要设置网络超时时间
	 * <br/>Set the network timeout, if <code>isUseNetworkTimeout</code> is
	 * <code>true</code> and the driver supports it.
	 *
	 * @param connection the connection to set the network timeout on
	 * @param timeoutMs  the number of milliseconds before timeout
	 * @throws SQLException throw if the connection.setNetworkTimeout() call throws
	 */
	private void setNetworkTimeout(final Connection connection, final long timeoutMs) throws SQLException {
		if (isNetworkTimeoutSupported == TRUE) {
			// 设置连接或连接中创建的对象等待数据库响应任何一个请求的最长时间。如果有任何请求仍然没有得到答复，则等待方法将返回一个SQLException，并且从该连接创建的连接或对象将被标记为已关闭。
			// 对象的任何后续使用，除了close、isClosed或Connection.isValid方法，将导致SQLException
			connection.setNetworkTimeout(netTimeoutExecutor, (int) timeoutMs);
		}
	}

	/**
	 * Execute the user-specified init SQL.
	 *
	 * @param connection the connection to initialize
	 * @param sql        the SQL to execute
	 * @param isCommit   whether to commit the SQL after execution or not
	 * @throws SQLException throws if the init SQL execution fails
	 */
	private void executeSql(final Connection connection, final String sql, final boolean isCommit) throws SQLException {
		if (sql != null) {
			try (Statement statement = connection.createStatement()) {
				// connection was created a few milliseconds before, so set query timeout is
				// omitted (we assume it will succeed)
				statement.execute(sql);
			}

			if (isIsolateInternalQueries && !isAutoCommit) {
				if (isCommit) {
					connection.commit();
				} else {
					connection.rollback();
				}
			}
		}
	}

	private void createNetworkTimeoutExecutor(final DataSource dataSource, final String dsClassName, final String jdbcUrl) {
		// Temporary hack for MySQL issue: http://bugs.mysql.com/bug.php?id=75615
		if ((dsClassName != null && dsClassName.contains("Mysql")) || (jdbcUrl != null && jdbcUrl.contains("mysql"))
				|| (dataSource != null && dataSource.getClass().getName().contains("Mysql"))) {
			netTimeoutExecutor = new SynchronousExecutor();
		} else {
			ThreadFactory threadFactory = config.getThreadFactory();
			threadFactory = threadFactory != null ? threadFactory
					: new DefaultThreadFactory(poolName + " network timeout executor", true);
			ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newCachedThreadPool(threadFactory);
			executor.setKeepAliveTime(15, SECONDS);
			executor.allowCoreThreadTimeOut(true);
			netTimeoutExecutor = executor;
		}
	}

	/**在指定的数据源上设置loginTimeout
	 * <br/>Set the loginTimeout on the specified DataSource.
	 *
	 * @param dataSource the DataSource
	 */
	private void setLoginTimeout(final DataSource dataSource) {
		if (connectionTimeout != Integer.MAX_VALUE) {
			try {
				dataSource.setLoginTimeout(Math.max(1, (int) MILLISECONDS.toSeconds(500L + connectionTimeout)));
			} catch (Throwable e) {
				LOGGER.info("{} - Failed to set login timeout for data source. ({})", poolName, e.getMessage());
			}
		}
	}

	/**
	 * This will create a string for debug logging. Given a set of "reset bits",
	 * this method will return a concatenated string, for example:
	 *
	 * Input : 0b00110 Output: "autoCommit, isolation"
	 *
	 * @param bits a set of "reset bits"
	 * @return a string of which states were reset
	 */
	private String stringFromResetBits(final int bits) {
		final StringBuilder sb = new StringBuilder();
		for (int ndx = 0; ndx < RESET_STATES.length; ndx++) {
			if ((bits & (0b1 << ndx)) != 0) {
				sb.append(RESET_STATES[ndx]).append(", ");
			}
		}

		sb.setLength(sb.length() - 2); // trim trailing comma
		return sb.toString();
	}

	// ***********************************************************************
	// Private Static Classes
	// ***********************************************************************

	static class ConnectionSetupException extends Exception {
		private static final long serialVersionUID = 929872118275916521L;

		ConnectionSetupException(Throwable t) {
			super(t);
		}
	}

	/**
	 * Special executor used only to work around a MySQL issue that has not been
	 * addressed. MySQL issue: http://bugs.mysql.com/bug.php?id=75615
	 */
	private static class SynchronousExecutor implements Executor {
		/** {@inheritDoc} */
		@Override
		public void execute(Runnable command) {
			try {
				command.run();
			} catch (Throwable t) {
				LoggerFactory.getLogger(PoolBase.class).debug("Failed to execute: {}", command, t);
			}
		}
	}

	interface IMetricsTrackerDelegate extends AutoCloseable {
		default void recordConnectionUsage(PoolEntry poolEntry) {
		}

		default void recordConnectionCreated(long connectionCreatedMillis) {
		}

		default void recordBorrowTimeoutStats(long startTime) {
		}

		default void recordBorrowStats(final PoolEntry poolEntry, final long startTime) {
		}

		default void recordConnectionTimeout() {
		}

		@Override
		default void close() {
		}
	}

	/**
	 * A class that delegates to a MetricsTracker implementation. The use of a
	 * delegate allows us to use the NopMetricsTrackerDelegate when metrics are
	 * disabled, which in turn allows the JIT to completely optimize away to
	 * callsites to record metrics.
	 */
	static class MetricsTrackerDelegate implements IMetricsTrackerDelegate {
		final IMetricsTracker tracker;

		MetricsTrackerDelegate(IMetricsTracker tracker) {
			this.tracker = tracker;
		}

		@Override
		public void recordConnectionUsage(final PoolEntry poolEntry) {
			tracker.recordConnectionUsageMillis(poolEntry.getMillisSinceBorrowed());
		}

		@Override
		public void recordConnectionCreated(long connectionCreatedMillis) {
			tracker.recordConnectionCreatedMillis(connectionCreatedMillis);
		}

		@Override
		public void recordBorrowTimeoutStats(long startTime) {
			tracker.recordConnectionAcquiredNanos(elapsedNanos(startTime));
		}

		@Override
		public void recordBorrowStats(final PoolEntry poolEntry, final long startTime) {
			final long now = currentTime();
			poolEntry.lastBorrowed = now;
			tracker.recordConnectionAcquiredNanos(elapsedNanos(startTime, now));
		}

		@Override
		public void recordConnectionTimeout() {
			tracker.recordConnectionTimeout();
		}

		@Override
		public void close() {
			tracker.close();
		}
	}

	/**
	 * A no-op implementation of the IMetricsTrackerDelegate that is used when
	 * metrics capture is disabled.
	 */
	static final class NopMetricsTrackerDelegate implements IMetricsTrackerDelegate {
	}
}
